/**
 * 
 */
package com.gragra.dependencyParsing;
import it.unimi.dsi.fastutil.doubles.DoubleArrayList;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import cern.colt.Arrays;
import com.gragra.data.MixingCorpus;
import com.gragra.parseOptions.ListOfDoubleOption;
import com.gragra.parseOptions.ListOfIntegerOption;
import com.gragra.parseOptions.ListOfStringsOption;
import com.gragra.parseOptions.Parser;
import com.gragra.sampling.PopulationProbabilityAssigner;
import com.gragra.sampling.PopulationSamplingStructure;
import com.gragra.sampling.RunSamplingPopulation;
import com.gragra.sampling.annealingSchedules.LinearAnnealingFromMaxToMin;
import com.gragra.sampling.binary.MakeCorpus;
import com.gragra.sampling.nonproj.DistHeadModel;
import com.gragra.sampling.sequentialStructure.WordAndTagHolder;
/**
 * @author Christoph Teichmann
 * 23.09.2013
 */
public class DisHeadModel implements OneInThreeOut
{
	/**
	 * 
	 */
	private double structAnnRed = 0.1;
	/**
	 * 
	 */
	private double structAnnMax = 25.0;
	/**
	 * 
	 */
	private static final String structMaxOpt = "stAnnMax";
	/**
	 * @return the structAnnRed
	 */
	protected double getStructAnnRed()
	{return this.structAnnRed;}
	/**
	 * @return the structAnnMax
	 */
	protected double getStructAnnMax()
	{return this.structAnnMax;}
	/**
	 * 
	 */
	private static final String structStepSizeOpt = "stAnnStep";
	/**
	 * 
	 */
	private double	annealStepSize = 0.1;
	/**
	 * 
	 */
	private final static String aStepSizeOpt = "annStepSize";
	/**
	 * 
	 */
	private double	annealMin = 1.0;
	/**
	 * @return the headAlpha
	 */
	protected double getHeadAlpha()
	{return this.headAlpha;}
	/**
	 * @return the stopAlpha
	 */
	protected double getStopAlpha()
	{return this.stopAlpha;}
	/**
	 * 
	 */
	private final static String aMinOpt = "annMin";
	/**
	 * 
	 */
	private double	annealMax = 20.0;
	/**
	 * 
	 */
	private final static String aMaxOpt = "annMax";
	/**
	 * 
	 */
	private int	samplingSteps = 5;
	/**
	 * 
	 */
	private final static String sampStepsOpt = "sampSteps";
	/**
	 * 
	 */
	private int	burnInSteps = 300;
	/**
	 * 
	 */
	private final static String burnInOpt = "burnIn";
	/**
	 * 
	 */
	private int	interSamplingSteps = 1;
	/**
	 * 
	 */
	private final static String interOpt = "interSteps";
	/**
	 * 
	 */
	private int	threads = 3;
	/**
	 * 
	 */
	private final static String threadsOpt = "chains";
	/**
	 * 
	 */
	private double	headAlpha = 0.1;
	/**
	 * 
	 */
	private final static String headAlOpt = "hAlpha";
	/**
	 * 
	 */
	private double	stopAlpha = 0.1;
	/**
	 * 
	 */
	private final static String stopAlOpt = "dAlpha";
	/**
	 * 
	 */
	private double	proposalSmoothing = 0.1;
	/**
	 * 
	 */
	private final static String propSmoothOpt = "proposalS";
	/**
	 * 
	 */
	private String	type = makeDefaultSampler();
	/**
	 * @return
	 */
	protected String makeDefaultSampler()
	{return SingleRootDependencyType.GreedyProjective.toString();}
	/**
	 * @return the type
	 */
	protected String getType()
	{return this.type;}
	/**
	 * 
	 */
	private final static String typeOpt = "sampType";
	/**
	 * 
	 */
	private double fertAlpha = 0.1;
	/**
	 * 
	 */
	private final static String fertAlphOpt = "fAlpha";
	/**
	 * 
	 * @param pa
	 */
	protected void setParameters(Parser pa)
	{
		this.annealStepSize = this.getValOrDefault(aStepSizeOpt, annealStepSize, pa);
		this.annealMin = this.getValOrDefault(aMinOpt, annealMin, pa);
		this.annealMax = this.getValOrDefault(aMaxOpt, annealMax, pa);
		this.samplingSteps = this.getValOrDefault(sampStepsOpt, samplingSteps, pa);
		this.burnInSteps = this.getValOrDefault(burnInOpt, burnInSteps, pa);
		this.interSamplingSteps = this.getValOrDefault(interOpt, interSamplingSteps, pa);
		this.threads = this.getValOrDefault(threadsOpt, threads, pa);
		this.headAlpha = this.getValOrDefault(headAlOpt, headAlpha, pa);
		this.stopAlpha = this.getValOrDefault(stopAlOpt, stopAlpha, pa);
		this.proposalSmoothing = this.getValOrDefault(propSmoothOpt, proposalSmoothing, pa);
		this.structAnnMax = this.getValOrDefault(structMaxOpt, this.structAnnMax, pa);
		this.structAnnRed = this.getValOrDefault(structStepSizeOpt, structAnnRed, pa);
		this.setFertAlpha(this.getValOrDefault(fertAlphOpt, getFertAlpha(), pa));
		ListOfStringsOption los = (ListOfStringsOption) pa.getInformation(typeOpt);
		if(los.hasBeenSet())
		{this.type = los.getData()[0];}
	}
	/**
	 * 
	 * @param name
	 * @param defau
	 * @param pa
	 * @return
	 */
	protected int getValOrDefault(String name,int defau, Parser pa)
	{
		ListOfIntegerOption ldo = (ListOfIntegerOption) pa.getInformation(name);
		if(ldo.hasBeenSet())
		{return ldo.getSettings()[0];}
		else
		{return defau;}
	}
	/**
	 * 
	 * @param name
	 * @param defau
	 * @param pa
	 * @return
	 */
	protected double getValOrDefault(String name,double defau, Parser pa)
	{
		ListOfDoubleOption ldo = (ListOfDoubleOption) pa.getInformation(name);
		if(ldo.hasBeenSet())
		{return ldo.getSettings()[0];}
		else
		{return defau;}
	}
	/* (non-Javadoc)
	 * @see com.gragra.dependencyParsing.OneInThreeOut#addParameters(com.gragra.parseOptions.Parser)
	 */
	@Override
	public void addParameters(Parser pa)
	{
		pa.addOption(aStepSizeOpt, new ListOfDoubleOption("sets the amount by which the annealing divisor is decreased, default is 0.1"));
		pa.addOption(aMinOpt, new ListOfDoubleOption("sets the minimum value of the annealing divisor, default value is 1.0"));
		pa.addOption(aMaxOpt, new ListOfDoubleOption("sets the maximum value of the annealing divisor, default value is 10.0"));
		pa.addOption(sampStepsOpt, new ListOfIntegerOption("sets the number of sampling steps that will be used default value is 500"));
		pa.addOption(burnInOpt, new ListOfIntegerOption("sets the number of burn in steps, default is 300"));
		pa.addOption(interOpt, new ListOfIntegerOption("sets the number of steps taken between samples, default value is 5"));
		pa.addOption(threadsOpt, new ListOfIntegerOption("sets the number of parallel chains that will be used, default value is 3"));;
		pa.addOption(headAlOpt, new ListOfDoubleOption(makeHeadAlphaDescript()));
		pa.addOption(stopAlOpt, new ListOfDoubleOption(makeStopAlphaDesc()));
		pa.addOption(propSmoothOpt, new ListOfDoubleOption("sets the amout of smoothing used in population based sampling, default value is 0.1"));
		pa.addOption(typeOpt, new ListOfStringsOption(makeTypeDesc()));
		pa.addOption(fertAlphOpt, new ListOfDoubleOption(makeFertDescription()));
		pa.addOption(structMaxOpt, new ListOfDoubleOption("sets the starting amount the constraint used in structural annealing"));
		pa.addOption(structStepSizeOpt, new ListOfDoubleOption("sets the reduction in the cosntraint used in structural annealing"));
	}
	/**
	 * @return
	 */
	protected String makeFertDescription()
	{return "sets the fertility alpha value, default is 0.1";}
	/**
	 * @return
	 */
	protected String makeTypeDesc()
	{return "sets the type of sampler used for individual trees options are: "+Arrays.toString(SingleRootDependencyType.values())+" default is "+this.type;}
	/**
	 * @return
	 */
	protected String makeStopAlphaDesc()
	{return "sets the alpha value for distances, default value is 0.1";}
	/**
	 * @return
	 */
	protected String makeHeadAlphaDescript()
	{return "sets the alpha value for head to head attachements, default is 0.1";}
	/* (non-Javadoc)
	 * @see com.gragra.dependencyParsing.OneInThreeOut#makeParses(com.gragra.parseOptions.Parser, java.io.BufferedReader, java.io.BufferedWriter, java.io.BufferedWriter, java.io.BufferedWriter)
	 */
	@Override
	public DoubleArrayList[] makeParses(Parser pa,BufferedReader input, BufferedWriter sampledOutput, BufferedWriter bestOutput) throws IOException, InterruptedException
	{
		setParameters(pa);
		MixingCorpus<? extends PopulationSamplingStructure> corp = makeCorpus(input);
		IntOpenHashSet is = new IntOpenHashSet();
		for(PopulationSamplingStructure pps : corp)
		{
			WordAndTagHolder npt = (WordAndTagHolder) pps;
			for(int i=0;i<npt.getLength();++i)
			{is.add(npt.getTag(i));}
		}
		PopulationProbabilityAssigner dmvNon = makeModel(is);
		RunSamplingPopulation rsp = makeRunner(corp,dmvNon);
		rsp.setThreads(threads);
		rsp.setAnn(new LinearAnnealingFromMaxToMin(annealMax, annealMin, annealStepSize));
		DoubleArrayList[] dal = rsp.run(dmvNon, burnInSteps, samplingSteps, interSamplingSteps);
		rsp.writeOut(sampledOutput);
		rsp.writeOutBest(bestOutput);
		sampledOutput.flush();
		bestOutput.flush();
		return dal;
	}
	/**
	 * 
	 * @param corp
	 * @param dmvNon
	 * @return
	 */
	protected RunSamplingPopulation makeRunner(
			MixingCorpus<? extends PopulationSamplingStructure> corp, PopulationProbabilityAssigner assigner)
	{return new RunSamplingPopulation(corp);}
	/**
	 * 
	 * @param is
	 * @return
	 */
	protected PopulationProbabilityAssigner makeModel(IntOpenHashSet is)
	{return new DistHeadModel(threads, is, headAlpha, stopAlpha, getFertAlpha());}
	/**
	 * @return the threads
	 */
	protected int getThreads()
	{return this.threads;}
	/**
	 * 
	 * @param input
	 * @return
	 * @throws IOException 
	 */
	protected MixingCorpus<? extends PopulationSamplingStructure> makeCorpus(BufferedReader input) throws IOException
	{
		MakeCorpus cnc = makeCreator();
		return cnc.createCorpus(input, threads, proposalSmoothing);
	}
	/**
	 * 
	 * @return
	 */
	protected MakeCorpus makeCreator()
	{return SingleRootDependencyType.valueOf(type).makeBaseCreator();}
	/**
	 * 
	 * @return
	 */
	public double getFertAlpha()
	{return fertAlpha;}
	/**
	 * 
	 * @param fertAlpha
	 */
	private void setFertAlpha(double fertAlpha)
	{this.fertAlpha = fertAlpha;}
}